/* INCLUDE FILES GO HERE */

#include <System.hpp> // include required definitions


////////////////////////////////////////////////////////////////////////////////////////////// WINDOW EXECUTION AND EVENTS //////////////////////////////////////////////////////////////////////////////////////////////


/* WINDOW MAIN */

// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	LPSTR lpCmdLine, int nCmdShow)
{
	// ------------------------------------------- Initialisation Area -------------------------------------------


	// Main application object
	cSystem sys;    // application object

	// Call Initialisation at run time

	// initialise window
	if (!sys.f_InitWindow(hInstance, sys.f_gethWnd(), nCmdShow))    // check function
	{
		ERROR(L"app.f_InitWindow() failed!")    // error message
			sys.f_CleanUpWindow();  // call quit
	}
	else
	{
		// initialise direct3d 11
		if (!sys.f_D3DInit(sys.f_gethWnd()))
		{
			ERROR(L"D3D11CreateDeviceAndSwapChain() failed!");      // error message
			sys.f_CleanUpWindow();  // call quit 
		}
	};



	// ------------------------------------------- Initialisation Area      -------------------------------------------



	///////////////////////////////////////////////////////////////////////////////////////////////////////////////



	// ------------------------------------------- Program Loop Area -------------------------------------------

	// Main program loop at run time
	if (!sys.f_RunEngine()) // check function
	{
		if (!sys.f_CleanUpD3D())
		{
			// clean up function calling
			if (!sys.f_CleanUpD3D());       // release direct3d 11
			if (!sys.f_CleanUpWindow());    // call quit            
		}
	}


	// ------------------------------------------- Program Loop Area -------------------------------------------



	///////////////////////////////////////////////////////////////////////////////////////////////////////////////



	// ------------------------------------------- Clean Up Area -------------------------------------------





	// ------------------------------------------- Clean Up Area -------------------------------------------
}


/* WINDOW PROCEDURE */

// this is the main message handler for the program
LRESULT CALLBACK cSystem::WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	// sort through and find what code to run for the message given
	switch (message)
	{
	case WM_KEYDOWN:
	{
		 if (wParam == VK_ESCAPE)
		 { // close the application entirely
		   f_CleanUpWindow();      // call quit 
		   return 0;
		 }

		 if (wParam == VK_F1)
		 { // close the application entirely
		   MSG(lpszTitleName)
			   return DefWindowProc(hWnd, message, wParam, lParam);
		 }

	} break;
	// this message is read when the window is closed
	case WM_DESTROY:
	{
					   // close the application entirely
					   f_CleanUpWindow();      // call quit 
					   return 0;
	} break;
	// close the window
	case WM_CLOSE:  // if the window X is clicked
	{
						// close the application entirely
						f_CleanUpWindow();      // call quit 
						return 0;
	} break;
	}

	// Handle any messages the switch statement didn't
	return DefWindowProc(hWnd, message, wParam, lParam);
}


////////////////////////////////////////////////////////////////////////////////////////////// WINDOW EXECUTION AND EVENTS //////////////////////////////////////////////////////////////////////////////////////////////



/////////////////////////////////////////////////////////////////////////////////////////////// WINDOW INITALISATION STUFF //////////////////////////////////////////////////////////////////////////////////////////////


/* WINDOW INITIALISATION GOES HERE */

// Window initialisation 
bool cSystem::f_InitWindow(HINSTANCE hInstance, HWND hWnd, int showWindow)
{
	// fill in the struct with the needed information
	m_wc.cbSize = sizeof(WNDCLASSEX);
	m_wc.style = CS_HREDRAW | CS_VREDRAW;
	m_wc.lpfnWndProc = WindowProc;
	m_wc.hInstance = hInstance;
	m_wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	m_wc.lpszClassName = lpszClassName;
	m_wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

	// Window class registration
	if (!RegisterClassEx(&m_wc))    // register the window class // check function
	{
		ERROR(L"RegisterClassEx() failed!"); // error message
		f_CleanUpWindow();      // call quit
	}
	else
	{
		// Adjustment of the window rect
		if (!AdjustWindowRect(&m_wr, WS_OVERLAPPEDWINDOW, FALSE))       // adjst the window rect style
		{
			UnregisterClass(lpszClassName, hInstance); // unregister window class
			ERROR(L"AdjustWindowRect() failed!") // error message
				f_CleanUpWindow();      // call quit 
		}
		else
		{
			// create the window and use the result as the handle
			g_hWnd = CreateWindowEx(NULL,

				// class, title and style
				lpszClassName,    // name of the window class
				lpszTitleName,   // title of the window
				WS_OVERLAPPEDWINDOW,    // window style

				// window draw coordinate and size of window in pixels
				WindowX,    // x-position of the window
				WindowY,    // y-position of the window
				m_wr.right - m_wr.left,    // width of the window
				m_wr.bottom - m_wr.top,    // height of the window

				// state we have no parent window, not using menu's, attatch to window instance, multiple window's = false
				NULL,    // we have no parent window, NULL
				NULL,    // we aren't using menus, NULL
				hInstance,    // application handle
				NULL);    // used with multiple windows, NULL

			// test the value returned by CreateWindowEx stored in g_hWnd
			if (g_hWnd == 0)        // check hWnd
			{
				DestroyWindow(hWnd);	// destroy the window handle
				UnregisterClass(lpszClassName, hInstance);	// unregister window class
				ERROR(L"CreateWindowEx() failed!")      // error message
					f_CleanUpWindow();      // call quit 
			}

			// display the window on the screen
			if (ShowWindow(g_hWnd, showWindow))   // check function
			{
				DestroyWindow(hWnd);	// destroy the window handle
				UnregisterClass(lpszClassName, hInstance);	// unregister window class
				ERROR(L"ShowWindow() failed!")      // error message
					f_CleanUpWindow();      // call quit 
			}
		};
	};
	return true; // return logic
}


// Main loop function
int cSystem::f_RunEngine(void)
{
	// init d3d

	while (TRUE)
	{
		if (PeekMessage(&m_msg, NULL, 0, 0, PM_REMOVE))
		{
			// translate keystroke messages into the right format
			TranslateMessage(&m_msg);

			// send the message to the WindowProc function
			DispatchMessage(&m_msg);

			// check to see if it's time to quit
			if (m_msg.message == WM_QUIT)
				break;
		}

		f_RenderFrame();        // this renders the a frame
	}

	// clean up com objects

	// return this part of the WM_QUIT message to Windows
	return m_msg.wParam;
}

// Clean up our window up on closing
bool cSystem::f_CleanUpWindow(void)
{
	PostQuitMessage(0);     // call quit 
	return true;
}

// Get the handle to the window
HWND cSystem::f_gethWnd(void)
{
	return g_hWnd;  // return the handle
}

/////////////////////////////////////////////////////////////////////////////////////////////// WINDOW INITALISATION STUFF //////////////////////////////////////////////////////////////////////////////////////////////

// Direct3d initialisation 
bool cSystem::f_D3DInit(HWND hWnd)      // call to initialise direct3d11
{
	// fill the swap chain description struct
	scd.BufferCount = 1;                                    // one back buffer
	scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;     // use 32-bit color
	scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;      // how swap chain is to be used
	scd.OutputWindow = g_hWnd;                              // the window to be used
	scd.SampleDesc.Count = 4;                               // how many multisamples
	scd.Windowed = TRUE;                                    // windowed/full-screen mode
	scd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;     // allow full-screen switching

	// create a device, device context and swap chain using the information in the scd struct
	D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, NULL,
		NULL, NULL, D3D11_SDK_VERSION, &scd,
		&m_d3dswapchain, &m_d3ddevice, NULL, &m_d3ddevcontext);

		// get the buffer attach to the texture
	m_d3dswapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&p_D3DBackBuffer);
	

	// use the back buffer address to create the render target
	m_d3ddevice->CreateRenderTargetView(p_D3DBackBuffer, NULL, &D3D_backbuffer);
	p_D3DBackBuffer->Release();

	// Depth stencil view desc
	m_dpethStencilDesc.Width = WindowWidthMax;	// depth view width
	m_dpethStencilDesc.Height = WindowHeightMax;	// depth view height
	m_dpethStencilDesc.MipLevels = 1;	// mipmap levels
	m_dpethStencilDesc.ArraySize = 1;	// array size, number of textures generated
	m_dpethStencilDesc.Format = DXGI_FORMAT_D32_FLOAT_S8X24_UINT;	// format of our buffer, 64bit
	m_dpethStencilDesc.SampleDesc.Count = sampleCount;	// multisampling count
	m_dpethStencilDesc.SampleDesc.Quality = sampleQuality;	// multisampling quality
	m_dpethStencilDesc.Usage = D3D11_USAGE_DEFAULT;	// how our texture will be used
	m_dpethStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;	// depth stencil bind
	m_dpethStencilDesc.CPUAccessFlags = 0;	// CPU access to the buffer
	m_dpethStencilDesc.MiscFlags = 0;	// misc flags

	// Create the texture and buffer
	m_d3ddevice->CreateTexture2D(&m_dpethStencilDesc, 0, &m_DepthStencilBuffer);	// create texture buffer
	m_d3ddevice->CreateDepthStencilView(m_DepthStencilBuffer, 0, &m_DepthStencilView);	// create depth stencil view

	// set the render target as the back buffer
	m_d3ddevcontext->OMSetRenderTargets(1, &D3D_backbuffer, m_DepthStencilView); // set render targets, back buffer and depth stencil view


	// Set the viewport
	// view port definition
	D3D_viewport.TopLeftX = WindowWidthMin;      // top left of the viewing area
	D3D_viewport.TopLeftY = WindowHeightMin;      // top right of the viewing area
	D3D_viewport.Width = WindowWidthMax;        // width of the viewing area
	D3D_viewport.Height = WindowHeightMax;       // height of the viewing area
	D3D_viewport.MinDepth = 0.0f;	// minimun depth value
	D3D_viewport.MaxDepth = 1.0f;	// maximum depth value

	m_d3ddevcontext->RSSetViewports(1, &D3D_viewport); // set view port

	return true;    // return logic
}

// Direct3D clean up
bool cSystem::f_CleanUpD3D(void)
{
	// full screen switching
	m_d3dswapchain->SetFullscreenState(FALSE, NULL);

	// initialisation
	m_d3dswapchain->Release();      // release the swapchain information
	m_d3ddevice->Release(); // release the device
	m_d3ddevcontext->Release();     // release the device context

	// rendering buffer(s)
	D3D_backbuffer->Release();	// render target view buffer
// CALLED JUST AFTER CREATION	p_D3DBackBuffer->Release();	// backbuffer texture
	m_DepthStencilBuffer->Release();	// depth stencil buffer
	m_DepthStencilView->Release();	// depth stencil view

	//window
	f_CleanUpWindow(); // finally make sure window is cleaned
	return true;    // return logic
}

// Direct3D render
bool cSystem::f_RenderFrame(void)
{
	m_d3ddevcontext->ClearRenderTargetView(D3D_backbuffer, BACKBUFFERCOLOR);        // clear the backbuffer ready for use

	// do 3D rendering on the back buffer here

	m_d3dswapchain->Present(0, 0);  // show backnuffer to the front buffer (the screen)

	return true;    // return logic
}